/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


// r_glState.cpp -- GL state manager


#include "r_local.h"


static glState_t	glState;


/*
 ==================
 GL_LoadIdentity
 ==================
*/
void GL_LoadIdentity (uint mode){

	switch (mode){
	case GL_PROJECTION:
		if (glState.projectionMatrix.IsIdentity())
			return;
		glState.projectionMatrix.Identity();

		break;
	case GL_MODELVIEW:
		if (glState.modelviewMatrix.IsIdentity())
			return;
		glState.modelviewMatrix.Identity();

		break;
	case GL_TEXTURE:
		if (glState.textureMatrix[glState.activeUnit].IsIdentity())
			return;
		glState.textureMatrix[glState.activeUnit].Identity();

		break;
	}

	qglMatrixMode(mode);
	qglLoadIdentity();
}

/*
 ==================
 GL_LoadMatrix
 ==================
*/
void GL_LoadMatrix (uint mode, const odMat4 &m){

	switch (mode){
	case GL_PROJECTION:
		if (glState.projectionMatrix == m)
			return;
		glState.projectionMatrix = m;

		break;
	case GL_MODELVIEW:
		if (glState.modelviewMatrix == m)
			return;
		glState.modelviewMatrix = m;

		break;
	case GL_TEXTURE:
		if (glState.textureMatrix[glState.activeUnit] == m)
			return;
		glState.textureMatrix[glState.activeUnit] = m;

		break;
	}

	qglMatrixMode(mode);
	qglLoadMatrixf(m.ToFloatPtr());
}

/*
 ==================
 GL_SelectTexture
 ==================
*/
void GL_SelectTexture (uint unit){

	if (glState.activeUnit == unit)
		return;

	glState.activeUnit = unit;

	qglActiveTexture(GL_TEXTURE0 + unit);
}

/*
 ==================
 GL_EnableTexture
 ==================
*/
void GL_EnableTexture (uint target){

	if (glState.activeTarget[glState.activeUnit] == target)
		return;

	if (glState.activeTarget[glState.activeUnit])
		qglDisable(glState.activeTarget[glState.activeUnit]);

	qglEnable(target);

	glState.activeTarget[glState.activeUnit] = target;
}

/*
 ==================
 GL_DisableTexture
 ==================
*/
void GL_DisableTexture (void){

	if (!glState.activeTarget[glState.activeUnit])
		return;

	qglDisable(glState.activeTarget[glState.activeUnit]);

	glState.activeTarget[glState.activeUnit] = 0;
}

/*
 ==================
 GL_BindTexture
 ==================
*/
void GL_BindTexture (texture_t *texture){

	if (texture->frameCount != tr.frameCount){
		texture->frameCount = tr.frameCount;

		tr.pc.textures++;
		tr.pc.textureBytes += texture->size;
	}

	if (glState.textureId[glState.activeUnit] == texture->textureId)
		return;

	glState.textureId[glState.activeUnit] = texture->textureId;

	qglBindTexture(texture->target, texture->textureId);
}

/*
 ==================
 GL_TexEnv
 ==================
*/
void GL_TexEnv (int texEnv){

	if (glState.texEnv[glState.activeUnit] == texEnv)
		return;

	glState.texEnv[glState.activeUnit] = texEnv;

	qglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, texEnv);
}

/*
 ==================
 GL_TexGen
 ==================
*/
void GL_TexGen (uint texCoord, int texGen){

	switch (texCoord){
	case GL_S:
		if (glState.texGen[glState.activeUnit][0] == texGen)
			return;
		glState.texGen[glState.activeUnit][0] = texGen;

		break;
	case GL_T:
		if (glState.texGen[glState.activeUnit][1] == texGen)
			return;
		glState.texGen[glState.activeUnit][1] = texGen;

		break;
	case GL_R:
		if (glState.texGen[glState.activeUnit][2] == texGen)
			return;
		glState.texGen[glState.activeUnit][2] = texGen;

		break;
	case GL_Q:
		if (glState.texGen[glState.activeUnit][3] == texGen)
			return;
		glState.texGen[glState.activeUnit][3] = texGen;

		break;
	}

	qglTexGeni(texCoord, GL_TEXTURE_GEN_MODE, texGen);
}

/*
 ==================
 GL_Enable
 ==================
*/
void GL_Enable (uint cap){

	switch (cap){
	case GL_CULL_FACE:
		if (glState.cullFace)
			return;
		glState.cullFace = true;

		break;
	case GL_POLYGON_OFFSET_FILL:
		if (glState.polygonOffsetFill)
			return;
		glState.polygonOffsetFill = true;

		break;
	case GL_BLEND:
		if (glState.blend)
			return;
		glState.blend = true;

		break;
	case GL_ALPHA_TEST:
		if (glState.alphaTest)
			return;
		glState.alphaTest = true;

		if (glConfig.samples && r_alphaToCoverage->integerValue)
			qglEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);

		break;
	case GL_DEPTH_TEST:
		if (glState.depthTest)
			return;
		glState.depthTest = true;

		break;
	case GL_STENCIL_TEST:
		if (glState.stencilTest)
			return;
		glState.stencilTest = true;

		break;
	case GL_TEXTURE_GEN_S:
		if (glState.textureGen[glState.activeUnit][0])
			return;
		glState.textureGen[glState.activeUnit][0] = true;

		break;
	case GL_TEXTURE_GEN_T:
		if (glState.textureGen[glState.activeUnit][1])
			return;
		glState.textureGen[glState.activeUnit][1] = true;

		break;
	case GL_TEXTURE_GEN_R:
		if (glState.textureGen[glState.activeUnit][2])
			return;
		glState.textureGen[glState.activeUnit][2] = true;

		break;
	case GL_TEXTURE_GEN_Q:
		if (glState.textureGen[glState.activeUnit][3])
			return;
		glState.textureGen[glState.activeUnit][3] = true;

		break;
	}

	qglEnable(cap);
}

/*
 ==================
 GL_Disable
 ==================
*/
void GL_Disable (uint cap){

	switch (cap){
	case GL_CULL_FACE:
		if (!glState.cullFace)
			return;
		glState.cullFace = false;

		break;
	case GL_POLYGON_OFFSET_FILL:
		if (!glState.polygonOffsetFill)
			return;
		glState.polygonOffsetFill = false;

		break;
	case GL_BLEND:
		if (!glState.blend)
			return;
		glState.blend = false;

		break;
	case GL_ALPHA_TEST:
		if (!glState.alphaTest)
			return;
		glState.alphaTest = false;

		if (glConfig.samples && r_alphaToCoverage->integerValue)
			qglDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);

		break;
	case GL_DEPTH_TEST:
		if (!glState.depthTest)
			return;
		glState.depthTest = false;

		break;
	case GL_STENCIL_TEST:
		if (!glState.stencilTest)
			return;
		glState.stencilTest = false;

		break;
	case GL_TEXTURE_GEN_S:
		if (!glState.textureGen[glState.activeUnit][0])
			return;
		glState.textureGen[glState.activeUnit][0] = false;

		break;
	case GL_TEXTURE_GEN_T:
		if (!glState.textureGen[glState.activeUnit][1])
			return;
		glState.textureGen[glState.activeUnit][1] = false;

		break;
	case GL_TEXTURE_GEN_R:
		if (!glState.textureGen[glState.activeUnit][2])
			return;
		glState.textureGen[glState.activeUnit][2] = false;

		break;
	case GL_TEXTURE_GEN_Q:
		if (!glState.textureGen[glState.activeUnit][3])
			return;
		glState.textureGen[glState.activeUnit][3] = false;

		break;
	}

	qglDisable(cap);
}

/*
 ==================
 GL_CullFace
 ==================
*/
void GL_CullFace (uint mode){

	if (glState.cullMode == mode)
		return;

	glState.cullMode = mode;

	qglCullFace(mode);
}

/*
 ==================
 GL_PolygonOffset
 ==================
*/
void GL_PolygonOffset (float factor, float units){

	if (glState.offsetFactor == factor && glState.offsetUnits == units)
		return;

	glState.offsetFactor = factor;
	glState.offsetUnits = units;

	qglPolygonOffset(factor, units);
}

/*
 ==================
 GL_BlendFunc
 ==================
*/
void GL_BlendFunc (uint src, uint dst){

	if (glState.blendSrc == src && glState.blendDst == dst)
		return;

	glState.blendSrc = src;
	glState.blendDst = dst;

	qglBlendFunc(src, dst);
}

/*
 ==================
 GL_AlphaFunc
 ==================
*/
void GL_AlphaFunc (uint func, float ref){

	if (glState.alphaFunc == func && glState.alphaRef == ref)
		return;

	glState.alphaFunc = func;
	glState.alphaRef = ref;

	qglAlphaFunc(func, ref);
}

/*
 ==================
 GL_DepthFunc
 ==================
*/
void GL_DepthFunc (uint func){

	if (glState.depthFunc == func)
		return;

	glState.depthFunc = func;

	qglDepthFunc(func);
}

/*
 ==================
 GL_StencilFunc
 ==================
*/
void GL_StencilFunc (uint func, int ref, uint mask){

	if (glState.stencilFunc == func && glState.stencilRef == ref && glState.stencilRefMask == mask)
		return;

	glState.stencilFunc = func;
	glState.stencilRef = ref;
	glState.stencilRefMask = mask;

	qglStencilFunc(func, ref, mask);
}

/*
 ==================
 GL_StencilOp
 ==================
*/
void GL_StencilOp (uint fail, uint zFail, uint zPass){

	if (glState.stencilFail == fail && glState.stencilZFail == zFail && glState.stencilZPass == zPass)
		return;

	glState.stencilFail = fail;
	glState.stencilZFail = zFail;
	glState.stencilZPass = zPass;

	qglStencilOp(fail, zFail, zPass);
}

/*
 ==================
 GL_ColorMask
 ==================
*/
void GL_ColorMask (bool red, bool green, bool blue, bool alpha){

	if (glState.colorMask[0] == red && glState.colorMask[1] == green && glState.colorMask[2] == blue && glState.colorMask[3] == alpha)
		return;

	glState.colorMask[0] = red;
	glState.colorMask[1] = green;
	glState.colorMask[2] = blue;
	glState.colorMask[3] = alpha;

	qglColorMask(red, green, blue, alpha);
}

/*
 ==================
 GL_DepthMask
 ==================
*/
void GL_DepthMask (bool mask){

	if (glState.depthMask == mask)
		return;

	glState.depthMask = mask;

	qglDepthMask(mask);
}

/*
 ==================
 GL_StencilMask
 ==================
*/
void GL_StencilMask (uint mask){

	if (glState.stencilMask == mask)
		return;

	glState.stencilMask = mask;

	qglStencilMask(mask);
}

/*
 ==================
 GL_DepthRange
 ==================
*/
void GL_DepthRange (float min, float max){

	if (glState.depthMin == min && glState.depthMax == max)
		return;

	glState.depthMin = min;
	glState.depthMax = max;

	qglDepthRange(min, max);
}

/*
 ==================
 GL_BindVertexBuffer
 ==================
*/
void GL_BindVertexBuffer (vertexBuffer_t *vertexBuffer){

	if (!vertexBuffer){
		if (!glState.bufferId)
			return;

		glState.bufferId = 0;

		qglBindBuffer(GL_ARRAY_BUFFER, 0);
		return;
	}

	if (vertexBuffer->frameCount != tr.frameCount){
		vertexBuffer->frameCount = tr.frameCount;

		if (vertexBuffer->dynamic){
			tr.pc.dynamicBuffers++;
			tr.pc.dynamicBufferBytes += vertexBuffer->size;
		}
		else {
			tr.pc.staticBuffers++;
			tr.pc.staticBufferBytes += vertexBuffer->size;
		}
	}

	if (glState.bufferId == vertexBuffer->bufferId)
		return;

	glState.bufferId = vertexBuffer->bufferId;

	qglBindBuffer(GL_ARRAY_BUFFER, vertexBuffer->bufferId);
}

/*
 ==================
 GL_SetDefaultState
 ==================
*/
void GL_SetDefaultState (void){

	int		i;

	// Reset the state manager
	glState.projectionMatrix.Identity();
	glState.modelviewMatrix.Identity();

	for (i = 0; i < MAX_TEXTURE_UNITS; i++)
		glState.textureMatrix[i].Identity();

	glState.activeUnit = 0;

	for (i = 0; i < MAX_TEXTURE_UNITS; i++){
		glState.activeTarget[i] = 0;

		glState.textureId[i] = 0;
		glState.texEnv[i] = GL_MODULATE;
		glState.texGen[i][0] = GL_OBJECT_LINEAR;
		glState.texGen[i][1] = GL_OBJECT_LINEAR;
		glState.texGen[i][2] = GL_OBJECT_LINEAR;
		glState.texGen[i][3] = GL_OBJECT_LINEAR;
	}

	glState.cullFace = false;
	glState.polygonOffsetFill = false;
	glState.blend = false;
	glState.alphaTest = false;
	glState.depthTest = false;
	glState.stencilTest = false;

	for (i = 0; i < MAX_TEXTURE_UNITS; i++){
		glState.textureGen[i][0] = false;
		glState.textureGen[i][1] = false;
		glState.textureGen[i][2] = false;
		glState.textureGen[i][3] = false;
	}

	glState.cullMode = GL_FRONT;
	glState.offsetFactor = 0.0f;
	glState.offsetUnits = 0.0f;
	glState.blendSrc = GL_ONE;
	glState.blendDst = GL_ZERO;
	glState.alphaFunc = GL_GREATER;
	glState.alphaRef = 0.0f;
	glState.depthFunc = GL_LEQUAL;
	glState.stencilFunc = GL_ALWAYS;
	glState.stencilRef = 128;
	glState.stencilRefMask = 255;
	glState.stencilFail = GL_KEEP;
	glState.stencilZFail = GL_KEEP;
	glState.stencilZPass = GL_KEEP;

	glState.colorMask[0] = GL_TRUE;
	glState.colorMask[1] = GL_TRUE;
	glState.colorMask[2] = GL_TRUE;
	glState.colorMask[3] = GL_TRUE;
	glState.depthMask = GL_TRUE;
	glState.stencilMask = 255;

	glState.depthMin = 0.0f;
	glState.depthMax = 1.0f;

	glState.bufferId = 0;

	// Set default state
	qglMatrixMode(GL_PROJECTION);
	qglLoadIdentity();

	qglMatrixMode(GL_MODELVIEW);
	qglLoadIdentity();

	for (i = MAX_TEXTURE_UNITS - 1; i >= 0; i--){
		if (i >= glConfig.maxTextureUnits)
			continue;

		qglActiveTexture(GL_TEXTURE0 + i);

		qglMatrixMode(GL_TEXTURE);
		qglLoadIdentity();

		qglDisable(GL_TEXTURE_2D);
		qglDisable(GL_TEXTURE_CUBE_MAP);

		qglBindTexture(GL_TEXTURE_2D, 0);
		qglBindTexture(GL_TEXTURE_CUBE_MAP, 0);

		qglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

		qglDisable(GL_TEXTURE_GEN_S);
		qglDisable(GL_TEXTURE_GEN_T);
		qglDisable(GL_TEXTURE_GEN_R);
		qglDisable(GL_TEXTURE_GEN_Q);

		qglTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
		qglTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
		qglTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
		qglTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
	}

	qglDisable(GL_CULL_FACE);
	qglCullFace(GL_FRONT);

	qglDisable(GL_POLYGON_OFFSET_FILL);
	qglPolygonOffset(0.0f, 0.0f);

	qglDisable(GL_BLEND);
	qglBlendFunc(GL_ONE, GL_ZERO);

	qglDisable(GL_ALPHA_TEST);
	qglAlphaFunc(GL_GREATER, 0.0f);

	qglDisable(GL_DEPTH_TEST);
	qglDepthFunc(GL_LEQUAL);

	qglDisable(GL_STENCIL_TEST);
	qglStencilFunc(GL_ALWAYS, 128, 255);
	qglStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

	qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	qglDepthMask(GL_TRUE);
	qglStencilMask(255);

	qglDepthRange(0.0f, 1.0f);

	qglShadeModel(GL_SMOOTH);

	qglFrontFace(GL_CCW);

	qglPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

	qglEnable(GL_SCISSOR_TEST);
	qglScissor(0, 0, glConfig.videoWidth, glConfig.videoHeight);

	if (glConfig.depthBoundsTestAvailable){
		qglEnable(GL_DEPTH_BOUNDS_TEST_EXT);
		qglDepthBoundsEXT(0.0f, 1.0f);
	}

	if (glConfig.samples){
		qglEnable(GL_MULTISAMPLE);

		qglDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
	}

	qglClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	qglClearDepth(1.0f);
	qglClearStencil(128);

	qglEnableClientState(GL_VERTEX_ARRAY);
	qglDisableClientState(GL_NORMAL_ARRAY);
	qglDisableClientState(GL_TEXTURE_COORD_ARRAY);
	qglDisableClientState(GL_COLOR_ARRAY);

	qglDisableVertexAttribArray(10);
	qglDisableVertexAttribArray(11);

	qglBindBuffer(GL_ARRAY_BUFFER, 0);

	qglUseProgram(0);
}

/*
 ==================
 GL_CheckForErrors
 ==================
*/
void GL_CheckForErrors (void){

	int		error;
	char	*string;

	if ((error = qglGetError()) == GL_NO_ERROR)
		return;

	switch (error){
	case GL_INVALID_ENUM:
		string = "GL_INVALID_ENUM";
		break;
	case GL_INVALID_VALUE:
		string = "GL_INVALID_VALUE";
		break;
	case GL_INVALID_OPERATION:
		string = "GL_INVALID_OPERATION";
		break;
	case GL_STACK_OVERFLOW:
		string = "GL_STACK_OVERFLOW";
		break;
	case GL_STACK_UNDERFLOW:
		string = "GL_STACK_UNDERFLOW";
		break;
	case GL_OUT_OF_MEMORY:
		string = "GL_OUT_OF_MEMORY";
		break;
	default:
		string = "UNKNOWN ERROR";
		break;
	}

	Com_Error(false, "GL_CheckForErrors: %s", string);
}
